﻿using System;

namespace BrasilFramework.Comum.Utilitario
{

    /// <summary>
    /// Classe para escrever números por extenso.
    /// </summary>
    public static class ValorPorExtenso
    {

        #region Implementação

        private static readonly string[] unidades = {
                                                           string.Empty, "um", "dois", "três", "quatro", "cinco", "seis", "sete",
                                                           "oito", "nove", "dez", "onze", "doze", "treze", "quatorze",
                                                           "quinze", "dezesseis", "dezessete", "dezoito", "dezenove"
                                                       };

        private static readonly string[] dezenas = {
                                                          string.Empty, "dez", "vinte", "trinta", "quarenta", "cinqüenta", "sessenta"
                                                          , "setenta", "oitenta", "noventa"
                                                      };


        private static readonly string[] centenas = {
                                                           string.Empty, "cem", "duzentos", "trezentos", "quatrocentos", "quinhentos"
                                                           , "seiscentos", "setecentos", "oitocentos", "novecentos"
                                                       };


        private const string mensagemErro = "Valor fora da faixa";
        private const decimal valorMinimo = 0.01M;
        private const decimal valorMaximo = 999999999999999.99M;
        private const string moeda = " real ";
        private const string moedas = " reais ";
        private const string centesimo = " centavo ";
        private const string centesimos = " centavos ";

        private static string Converter(long numero)
        {
            long resto;

            if ((numero >= 1) && (numero <= 19))
                return unidades[numero];

            if ((numero == 20) || (numero == 30) || (numero == 40) ||
                (numero == 50) || (numero == 60) || (numero == 70) ||
                (numero == 80) || (numero == 90))
                return dezenas[Math.DivRem(numero, 10, out resto)] + " ";

            if ((numero >= 21) && (numero <= 29) ||
                (numero >= 31) && (numero <= 39) ||
                (numero >= 41) && (numero <= 49) ||
                (numero >= 51) && (numero <= 59) ||
                (numero >= 61) && (numero <= 69) ||
                (numero >= 71) && (numero <= 79) ||
                (numero >= 81) && (numero <= 89) ||
                (numero >= 91) && (numero <= 99))
                return dezenas[Math.DivRem(numero, 10, out resto)] + " e " + Converter(numero % 10);

            if ((numero == 100) || (numero == 200) || (numero == 300) ||
                (numero == 400) || (numero == 500) || (numero == 600) ||
                (numero == 700) || (numero == 800) || (numero == 900))
                return centenas[Math.DivRem(numero, 100, out resto)] + " ";

            if ((numero >= 101) && (numero <= 199))
                return " cento e " + Converter(numero % 100);

            if ((numero >= 201) && (numero <= 299) ||
                (numero >= 301) && (numero <= 399) ||
                (numero >= 401) && (numero <= 499) ||
                (numero >= 501) && (numero <= 599) ||
                (numero >= 601) && (numero <= 699) ||
                (numero >= 701) && (numero <= 799) ||
                (numero >= 801) && (numero <= 899) ||
                (numero >= 901) && (numero <= 999))
                return centenas[Math.DivRem(numero, 100, out resto)] + " e " +
                       Converter(numero % 100);

            if ((numero >= 1000) && (numero <= 999999))
                return Converter(Math.DivRem(numero, 1000, out resto)) + " mil " +
                       Converter(numero % 1000);

            if ((numero >= 1000000) && (numero <= 1999999))
                return Converter(Math.DivRem(numero, 1000000, out resto)) + " milhão " +
                       Converter(numero % 1000000);

            if ((numero >= 2000000) && (numero <= 999999999))
                return Converter(Math.DivRem(numero, 1000000, out resto)) + " milhões " +
                       Converter(numero % 1000000);

            if ((numero >= 1000000000) && (numero <= 1999999999))
                return Converter(Math.DivRem(numero, 1000000000, out resto)) + " bilhão " +
                       Converter(numero % 1000000000);

            if ((numero >= 2000000000) && (numero <= 999999999999))
                return Converter(Math.DivRem(numero, 1000000000, out resto)) + " bilhões " +
                       Converter(numero % 1000000000);

            if ((numero >= 1000000000000) && (numero <= 1999999999999))
                return Converter(Math.DivRem(numero, 1000000000000, out resto)) + " trilhão " +
                       Converter(numero % 1000000000000);

            if ((numero >= 2000000000000) && (numero <= 999999999999999))
                return Converter(Math.DivRem(numero, 1000000000000, out resto)) + " trilhões " +
                       Converter(numero % 1000000000000);

            return string.Empty;
        }

        private static string LimparEspacos(string texto)
        {
            string strRetorno = string.Empty;
            bool booUltIs32 = false;

            foreach (char chrChar in texto)
            {
                if (chrChar != 32)
                {
                    strRetorno += chrChar;
                    booUltIs32 = false;
                }
                else if (!booUltIs32)
                {
                    strRetorno += chrChar;
                    booUltIs32 = true;
                }
            }

            return strRetorno.Trim();
        }

        #endregion

        #region Métodos de Extensão

        public static string ParaExtenso(this decimal numero)
        {
            return NumeroParaExtenso(numero);
        }

        public static string ParaExtenso(this int numero)
        {
            return NumeroParaExtenso(numero);
        }

        #endregion

        public static string NumeroParaExtenso(decimal decNumero)
        {
            string texto = string.Empty;

            if ((decNumero >= valorMinimo) && (decNumero <= valorMaximo))
            {
                long inteiro = Convert.ToInt64(Math.Truncate(decNumero));
                long centavos = Convert.ToInt64(Math.Truncate((decNumero - Math.Truncate(decNumero)) * 100));

                texto += Converter(inteiro) + (inteiro <= 1 ? moeda : moedas);

                if (centavos > 0)
                    texto += " e " + Converter(centavos) + (centavos == 1 ? centesimo : centesimos);
            }
            else
                throw new Exception(mensagemErro);

            return LimparEspacos(texto);
        }

    }
}